﻿using UnityEngine;
using System.Collections;
using System.Text;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEditor.SceneManagement;

namespace ElementSystem
{

    public static class CodeLinkerEditorCsUtils
    {

        private static Type _superClass;

        public static Component GetMainComponent(GameObject tree)
        {
            var objName = tree.name;
            Component comp;
            comp = tree.GetComponent(objName);
            if (comp != null)
            {
                return comp;
            }
            var monoList = tree.GetComponents<MonoBehaviour>();
            foreach(var mono in monoList)
            {
                if(mono.GetType() == typeof(CodeLinkerObject))
                {
                    continue;
                }
                return mono;
            }
            return null;
        }

        public static void GenerateCodeForTree(CodeLinkerObject tree)
        {

            //var mainComponent = tree.GetComponent(tree.name);
            var mainComponent = GetMainComponent(tree.gameObject);

            if (mainComponent == null)
            {
                throw new Exception("[CodeLinker] mainComponent not found");
            }


            //_superClass = mainComponent.GetType().BaseType;
            var sb = new StringBuilder();

            var refTypeToTrueDic = new Dictionary<Type, bool>();
            //string className = tree.GetClassName();
            var className = mainComponent.GetType().Name;
            var nameSpace = mainComponent.GetType().Namespace;

            // 寻找所有$变量transform
            var transformToTreePathDic = new Dictionary<Transform, string>();
            FindMark(true, tree.transform, transformToTreePathDic, "");

            WriteClassComment(sb);

            //string superClassName = _superClass.Name;
            //refTypeToTrueDic[_superClass] = true;
            if(!string.IsNullOrEmpty(nameSpace))
            {
                sb.AppendLine("namespace " + nameSpace);
                sb.AppendLine("{");
            }

            sb.AppendLine("public partial class " + className);
            sb.AppendLine("{");

            var commonNameToListInfoDic = new Dictionary<string, ListInfo>();

            // 生成属性
            foreach (var kv in transformToTreePathDic)
            {
                var transform = kv.Key;
                var path = kv.Value;
                WriteCodeForOneElement(sb, refTypeToTrueDic, commonNameToListInfoDic, transform, path);
            }

            // 生成额为列表属性
            foreach(var kv in commonNameToListInfoDic)
            {
                var info = kv.Value;
                var orderedElementList = info.OrderedElementList;
                var listName = info.ListVariableName;
                var publication = info.publication;
                var publicationString = publication == Publication.Public ? "public" : "protected";
                var backfieldName = ToBackfeildFormat(listName);
                var propertyName = ToProertyFormat(listName);
                var type = info.elementType;
                var typeName = type.Name;


                if (orderedElementList.Count > 0)
                {
                    refTypeToTrueDic[typeof(List<>)] = true;

                    sb.AppendLine($"\tprivate List<{typeName}> {backfieldName};");
                    sb.AppendLine($"\t{publicationString} List<{typeName}> {propertyName}");
                    sb.AppendLine("\t{");
                    sb.AppendLine("\t\tget");
                    sb.AppendLine("\t\t{");
                    sb.AppendLine($"\t\t\tif({backfieldName} == null)");
                    sb.AppendLine("\t\t\t{");
                    sb.AppendLine($"\t\t\t\tvar list = new List<{typeName}>()");
                    sb.AppendLine("\t\t\t\t{");
                    foreach(var element in orderedElementList)
                    {
                        sb.AppendLine($"\t\t\t\t\tthis.{element},");
                    }

                    sb.AppendLine("\t\t\t\t};");
                    sb.AppendLine($"\t\t\t\tthis.{backfieldName} = list;");
                    sb.AppendLine("\t\t\t}");
                    sb.AppendLine($"\t\t\treturn {backfieldName};");
                    sb.AppendLine("\t\t}");
                    sb.AppendLine("\t}");
                    sb.AppendLine();
                }
            }

            // 结束部分类
            sb.AppendLine("}");

            if (!string.IsNullOrEmpty(nameSpace))
            {
                sb.AppendLine("}");
            }

            var codeWithOutUsing = sb.ToString();
            var usignString = GenerateUsingCode(refTypeToTrueDic);
            var code = usignString + "\r\n" + codeWithOutUsing;

            // 写文件
            var filePath = ResolveCodeFilePath(className);
            File.WriteAllText(filePath, code);
            AssetDatabase.Refresh();
            Debug.Log("[CodeLinker] code generated at: " + filePath);
        }

        static void WriteClassComment(StringBuilder sb)
        {
            var nowDate = DateTime.Now;
            var timeString = nowDate.ToString("yyyy/MM/dd HH:mm:ss");
            sb.AppendLine("// This file was auto generated by CodeLinker");
            sb.AppendLine($"// update time : " + timeString);
        }

        static string GenerateUsingCode(Dictionary<Type, bool> refTypeToTrueDic)
        {
            var dic = new Dictionary<string, bool>();
            foreach(var kv in refTypeToTrueDic)
            {
                var type = kv.Key;
                var ns = type.Namespace;
                if (ns != null)
                {
                    dic[ns] = true;
                }
            }

            var sb = new StringBuilder();
            foreach (var kv in dic)
            {
                var ns = kv.Key;
                sb.AppendLine($"using {ns};");
            }
            var code = sb.ToString();
            return code;
        }

        class ListInfo
        {
            public string commonName;
            public Publication publication;
            public Type elementType;
            public List<string> elementNameList = new List<string>();

            public void AddElement(string name)
            {
                elementNameList.Add(name);
            }

            public string ListVariableName
            {
                get
                {
                    var ret = ToListVariableName(this.commonName);
                    return ret;
                }
            }

            public List<string> OrderedElementList
            {
                get
                {
                    var orderedElementList = new List<string>();
                    for (int i = 0; i < elementNameList.Count; i++)
                    {
                        var elementName = $"{commonName}_{i}";
                        if (elementNameList.Contains(elementName))
                        {
                            orderedElementList.Add(elementName);
                        }
                        else
                        {
                            break;
                        }
                    }
                    return orderedElementList;
                }
            }
        }

        static void WriteCodeForOneElement(StringBuilder sb, Dictionary<Type, bool> refTypeToTrueDic, Dictionary<string, ListInfo> commonNameToListInfoDic, Transform tranfrom, string path)
        {
            if (path == "")
            {
                // 根结点不需要生成索引
                return;
            }
            var publication = GetPublication(tranfrom.name);
            var variableName = tranfrom.name;
            variableName = FixVariableName(variableName);
            var propertyName = ToProertyFormat(variableName);
            var backfeildName = ToBackfeildFormat(variableName);
            // 决定type
            var type = DecideType(tranfrom);
            var typeName = type.Name;
            refTypeToTrueDic[type] = true;

            WriteCodeForOneElement(sb, publication, typeName, backfeildName, propertyName, path);

            // 处理可能的数组
            var parts = propertyName.Split('_');
            if(parts.Length == 1)
            {
                return;
            }
            // 是否数组结尾
            var lastPart = parts[parts.Length - 1];
            var isLastPartNumber = int.TryParse(lastPart, out int _);
            if(!isLastPartNumber)
            {
                return;
            }

            // 所属数组名
            var partsWithoutLast = new string[parts.Length - 1];
            for(int i = 0; i < parts.Length - 1; i ++)
            {
                partsWithoutLast[i] = parts[i];
            }
            var commonName = string.Join("_", partsWithoutLast);
            
            // 储存
            var b = commonNameToListInfoDic.ContainsKey(commonName);
            if (!b)
            {
                var info = new ListInfo();
                info.commonName = commonName;
                info.publication = publication;
                info.elementType = type;
                commonNameToListInfoDic[commonName] = info;
            }
            var list = commonNameToListInfoDic[commonName];
            list.AddElement(propertyName);
        }

        static void WriteCodeForOneElement(StringBuilder sb, Publication publication, string typeName, string backfeildName, string propertyName, string path)
        {
            var publicationString = publication == Publication.Public ? "public" : "protected";

            sb.AppendLine("\tprivate " + typeName + " " + backfeildName + ";");
            sb.AppendLine($"\t{publicationString} " + typeName + " " + propertyName);
            sb.AppendLine("\t{");
            sb.AppendLine("\t\tget");
            sb.AppendLine("\t\t{");
            sb.AppendLine("\t\t\tif(" + backfeildName + " == null)");
            sb.AppendLine("\t\t\t{");
            sb.AppendLine("\t\t\t\tvar t = CodeLinkerUtil.FindByPath(transform, \"" + path + "\");");
            sb.AppendLine("\t\t\t\t" + backfeildName + " = t.gameObject.GetComponent<" + typeName + ">();");
            sb.AppendLine("\t\t\t}");
            sb.AppendLine("\t\t\treturn " + backfeildName + ";");
            sb.AppendLine("\t\t}");
            sb.AppendLine("\t}");
            sb.AppendLine();
        }

        public enum Publication
        {
            Proteced,
            Public,
        }

        static Publication GetPublication(string gameObjectName)
        {
            if(gameObjectName.StartsWith("$$"))
            {
                return Publication.Public;
            }
            else
            {
                return Publication.Proteced;
            }
        }

        static string FixVariableName(string variableName)
        {
            variableName = variableName.Trim('$');
            variableName = variableName.Trim('@');
            variableName = variableName.Replace("$", "_");
            variableName = variableName.Replace("@", "_");
            variableName = variableName.Replace(" ", "_");
            variableName = variableName.Replace("+", "_");
            variableName = variableName.Replace("-", "_");
            variableName = variableName.Replace(".", "_");
            variableName = variableName.Replace("<", "_");
            variableName = variableName.Replace(">", "_");
            variableName = variableName.Replace("[", "_");
            variableName = variableName.Replace("]", "_");
            variableName = variableName.Trim('_');
            return variableName;
        }

        private static Type DecideType(Transform tranfrom)
        {
            //var hotCreator = tranfrom.GetComponent<HotBehaviorCreator>();
            //if(hotCreator != null)
            //{
            //    var type = hotCreator.ClassType;
            //    var name = type.Name;
            //    return name;
            //}

            var compponentSerachTypeList = new Type[]
            {
                    //typeof(Canvas),
                    //typeof(ParticleSystem),
                    //typeof(UnityEngine.Video.VideoPlayer),
                    typeof(MonoBehaviour),
                    typeof(Component),
                    
            };
            foreach (var targetType in compponentSerachTypeList)
            {
                var comList = tranfrom.GetComponents(targetType);
                if (comList.Length == 0)
                {
                    continue;
                }
                foreach (var com in comList)
                {
                    var type = com.GetType();
                    if (type == typeof(CodeLinkerObject))
                    {
                        continue;
                    }
                    if (type == typeof(Transform))
                    {
                        continue;
                    }
                    if(type == typeof(RectTransform))
                    {
                        continue;
                    }
                    return type;
                }
            }

            if(tranfrom.GetComponent<RectTransform>() != null)
            {
                //return "RectTransform";
                return typeof(RectTransform);
            }
            else
            {
                //return "Transform";
                return typeof(Transform);
            }
        }

        private static void FindMark(bool isRoot, Transform t, Dictionary<Transform, string> dic, string parentPath)
        {
            if (t.name.Contains("/"))
            {
                throw new Exception($"transform name can't contains '/' ({t.name}, ) ");
            }
            string path;
            if (isRoot)
            {
                path = "";
            }
            else
            {
                path = parentPath == "" ? t.name : parentPath + "/" + t.name;
            }
            bool rescure = true;
            if (t.name.StartsWith("$"))
            {
                dic[t] = path;
            }
            else if (t.name.StartsWith("@"))
            {
                dic[t] = path;
                rescure = false;
            }
            else if (t.name.StartsWith("."))
            {
                rescure = false;
            }

            if (!isRoot)
            {
                if (rescure)
                {
                    if (t.GetComponent<CodeLinkerObject>() != null)
                    {
                        rescure = false;
                    }
                }
            }

            if (rescure)
            {
                for (int i = 0; i < t.childCount; i++)
                {
                    var child = t.GetChild(i);
                    FindMark(false, child, dic, path);
                }
            }

        }

        private static string ToListVariableName(string name)
        {
            if (name.EndsWith("List"))
            {
                return name;
            }
            else
            {
                return name + "List";
            }
        }

        private static string ToProertyFormat(string name)
        {
            return name.Substring(0,1).ToUpper() + name.Substring(1);
        }

        private static string ToBackfeildFormat(string name)
        {
            return "_" + name.Substring(0,1).ToLower() + name.Substring(1);
        }

        public static string FindCodeFilePath(string codeFileName)
        {
            var searchStr = codeFileName + " t:script";
            var idList = AssetDatabase.FindAssets(searchStr);
            //Debug.Log(searchStr);
            foreach (var id in idList)
            {
                var path = AssetDatabase.GUIDToAssetPath(id);
                //Debug.Log(path);
                var fileName = Path.GetFileName(path);
                if (fileName == codeFileName + ".cs")
                {
                    return path;
                }
            }
            return null;
        }

        public static string ResolveCodeFilePath(string className)
        {
            var fileName = className + "+";
            {
                var filePath = FindCodeFilePath(fileName);
                if (!string.IsNullOrEmpty(filePath))
                {
                    return filePath;
                }
            }
            {
                var classPath = FindCodeFilePath(className);
                if (!string.IsNullOrEmpty(classPath))
                {
                    var dir = Path.GetDirectoryName(classPath);
                    var path = $"{dir}/{fileName}.cs";
                    return path;
                }
            }


            var generateCodeDir = "Assets/Gen/CodeLinkerGen";
            if (!Directory.Exists(generateCodeDir))
            {
                Directory.CreateDirectory(generateCodeDir);
            }
            var newFilePath = generateCodeDir + "/" + fileName + ".cs";
            return newFilePath;
        }

    }

}